/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.kth.lwr.jeat.utils;

import java.util.*;
import javax.measure.unit.Unit;
import se.kth.lwr.jeat.core.AmountDataSource;
import se.kth.lwr.jeat.core.AmountFunction;

/** Converts Amounts by use of AmountFunctoins. The constructor
 * takes the "natural" unit that do not need conversion. Support
 * for additional Units can be added through setAcceptFunction method.
 *
 * @author davidev
 */
public class DefaultAmountConverter implements AmountConverter{
    
    /**
     * A map which keys are sensitive to units compatibility. UnitCompabiltyWrappers
     * are considered equals if the unit wrapped in them are compatible. The 
     * corresponding value in the map is an AmountFunction that holds the value
     * of the factor which is required to be multiplied with the key 
     * (Unit) to retrieve a desired unit. 
     */
    Map<UnitCompatibiliyWrapper, AmountFunction> supportedunits=new HashMap<>();
    
    /**
     * Creates an AmountConverter that accepts the Unit
     * provided by the parameter without conversion.
     * 
     * @param natural the compatible unit that need no conversion
     */

    public DefaultAmountConverter(Unit natural) {
        UnitCompatibiliyWrapper a=new UnitCompatibiliyWrapper(natural);
        supportedunits.put(a, new AmountFunction());
    }
    
    /** <p>
     * Associates a specific unit with an AmountFunction. This means that
     * AmountDataSources (A) with the specified Unit (X) can be processed by this 
     * AmountConverter through multiplication by the given AmountFunction (Y) and
     * thereby be converted to the desired unit (R).
     * </p>
     * R = A * Y
     * 
     * 
     * @param unit the unit (X)
     * @param multiplyer the associated AmountFunction (Y)
     */
    
    public void setAcceptFunction(Unit unit, AmountFunction multiplyer){
        UnitCompatibiliyWrapper u=new UnitCompatibiliyWrapper(unit);
        supportedunits.put(u, multiplyer);
        
    }
    
    /**
     * Tries to convert the passed amount to an amount of the natural unit 
     * of this converter. Throws an IllegalArgumentException if conversion
     * is not possible. If the passed amount is already compatible with the 
     * natural unit, it is returned as an AmountFunction.
     * 
     * @param amount
     * @return 
     */
    @Override
    public AmountFunction toCompatibleAmount(AmountDataSource amount) {
        //Unit of the dataSource
        Unit unit=amount.readValue().getUnit();
        //wraps it
        UnitCompatibiliyWrapper ucc=new UnitCompatibiliyWrapper(unit);
        //Search for compatible units
        AmountFunction multiplier=supportedunits.get(ucc);
        if(multiplier==null){
            String message="Unsupported unit try with";
            for (UnitCompatibiliyWrapper u : supportedunits.keySet()) {
                message+=" " +u.toString()+",";
            
            }
            throw new IllegalArgumentException(message);
        }else{
            //AmountFunctions are not immutable (at the moment)
            //ned to wrap into new multiplier
            return new AmountFunction(multiplier).multiply(amount);
        }        
    }

    /**
     * 
     * @param amount
     * @return true if conversion of the passed amount are supported, false otherwise
     */
    @Override
    public boolean isSupported(AmountDataSource amount) {
        return supportedunits.containsKey(amount.readValue().getUnit());

    }
    
    /**
     * this is a unit wrapper class which behaves as units that are 
     * compatible are equals.
     */
    class UnitCompatibiliyWrapper {
        Unit unit;

        public UnitCompatibiliyWrapper(Unit base) {
            unit=base;
        }
        
        public Unit getUnit(){
            return unit;
        }
        
        /**Version of equals that return true if units are compatible
         * 
         * @param obj
         * @return 
         */

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final UnitCompatibiliyWrapper other = (UnitCompatibiliyWrapper) obj;
            if (!this.unit.isCompatible(other.getUnit())) {
                return false;
            }
            return true;
        }
        
        /**According to JScience doc, the isComaptible(Unit u) method returns
         * getDimension.equals(u.getDimension). Thus, to maintain the general contract 
         * between hashCode and Equals, I use the value of the units dimension
         * in the hashCode function.
         * 
         * 
         * @return 
         */

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 89 * hash + Objects.hashCode(this.getUnit().getDimension());
            return hash;
        }

        

        
    }
    
    
    
}
